/*
*
==============================================================================
*  Name        : 
*  Part of     : Omen Game Engine - DirectX 10 Plugin
*  Description : 
*  Version     : 1.0
*
*  Copyright (c) 2013 Lauri Kortevaara.
* 
==============================================================================
*/

#include "DirectX_10_GraphicsDriver.h"

using namespace Omen;
using namespace std;

#define fullscreen false


DirectX_10_GraphicsDriver::DirectX_10_GraphicsDriver()
{
    m_bIsActive = false;

    m_pCurrentAdapter = 0;
}

//=============================================================================
/**
** <summary>Creates a renderer</summary>
**
*/
Omen::IRenderer* DirectX_10_GraphicsDriver::createRenderer() 
{
    m_renderer = NULL;

    initialize();

    return m_renderer;
}


//=============================================================================
/**
** <summary>Destructor release the renderer and DX 10 interfaces </summary>
**
*/
DirectX_10_GraphicsDriver::~DirectX_10_GraphicsDriver()
{
    delete m_renderer;
    m_renderer = NULL;

    // Release the adapter output.
    m_pCurrentOutput->Release();
    m_pCurrentOutput = 0;

    // Release the adapter.
    m_pCurrentAdapter->Release();
    m_pCurrentAdapter = 0;

    // Release the factory.
    m_pDXGIFactory->Release();
    m_pDXGIFactory = 0;
}

//=============================================================================
/**
**  <summary> This will Create the DirectX_10_ Graphics Infrastructure Factory Interface</summary>
**
*/
bool DirectX_10_GraphicsDriver::create_DXGI_Factory()
{
    // Create a DirectX graphics interface factory.
    HRESULT result = CreateDXGIFactory(__uuidof( IDXGIFactory), (void**)&m_pDXGIFactory);
    return SUCCEEDED(result);
}

//=============================================================================
/**
**  <summary> This will Enumerate all graphics adapters existing in the system</summary>
**
*/
bool DirectX_10_GraphicsDriver::enumerate_DXGI_Adapters()
{
    bool bOk = false;
    if( m_pDXGIFactory )
    {
        UINT i = 0; 
        IDXGIAdapter * pAdapter; 

        while(m_pDXGIFactory->EnumAdapters( i, &pAdapter) != DXGI_ERROR_NOT_FOUND) 
        { 
            m_vDXGIAdapters.push_back(pAdapter); 
            ++i; 
        } 

        bOk = true;
    }
    return bOk;
}

bool DirectX_10_GraphicsDriver::enumerate_DXGI_AdapterOutputs( IDXGIAdapter* pAdapter )
{
    bool bOk = false;
    
    if( !pAdapter )
        pAdapter = m_pCurrentAdapter;

    if( pAdapter )
    {
        UINT i = 0;
        IDXGIOutput * pOutput;
        std::vector<IDXGIOutput*> vOutputs;
        while(pAdapter->EnumOutputs( i, &pOutput) != DXGI_ERROR_NOT_FOUND)
        {
            m_mapDXGIOutputs[pAdapter].push_back(pOutput);
            ++i;
        }
        bOk = true;
    }

    return bOk;
}

//=============================================================================
/**
** <summary>Creates the DXGI SwapChain</summary>
**
*/
bool DirectX_10_GraphicsDriver::create_SwapChain()
{
    bool bOk = false;
    HRESULT result = S_OK;

    DXGI_SWAP_CHAIN_DESC swapChainDesc;
      
    IDXGISwapChain* swapChain;
    ID3D10Device*   device;
    
    // Initialize the swap chain description.
    ZeroMemory(&swapChainDesc, sizeof(swapChainDesc));

    // Set to a single back buffer.
    swapChainDesc.BufferCount = 1;

    // Set the width and height of the back buffer.
    swapChainDesc.BufferDesc.Width = m_renderer->m_window.width();
    swapChainDesc.BufferDesc.Height =m_renderer->m_window.height();

    // Set regular 32-bit surface for the back buffer.
    swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;

    // Set the refresh rate of the back buffer.
    if(m_vsync_enabled)
    {
        swapChainDesc.BufferDesc.RefreshRate.Numerator =   m_screenRefreshRate.Numerator;
        swapChainDesc.BufferDesc.RefreshRate.Denominator = m_screenRefreshRate.Denominator;
    }
    else
    {
        swapChainDesc.BufferDesc.RefreshRate.Numerator = 0;
        swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
    }

    // Set the usage of the back buffer.
    swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;

    // Set the handle for the window to render to.
    swapChainDesc.OutputWindow = reinterpret_cast<HWND>(m_renderer->m_window.windowHandle());

    // Turn multisampling off.
    swapChainDesc.SampleDesc.Count = 1;
    swapChainDesc.SampleDesc.Quality = 0;

    // Set to full screen or windowed mode.
    if(fullscreen)
    {
        swapChainDesc.Windowed = false;
    }
    else
    {
        swapChainDesc.Windowed = true;
    }

    // Set the scan line ordering and scaling to unspecified.
    swapChainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
    swapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;

    // Discard the back buffer contents after presenting.
    swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;

    // Don't set the advanced flags.
    swapChainDesc.Flags = 0;

  

    // Create the swap chain and the Direct3D device.
    result = D3D10CreateDeviceAndSwapChain( NULL,                           // [in] IDXGIAdapter* adapter
                                            D3D10_DRIVER_TYPE_HARDWARE,     // [in] D3D10_DRIVER_TYPE [_HARDWARE,_REFERENCE,_NULL,_SOFTWARE,_WARP]
                                            NULL,                           // [in] HMODULE (Software handle)
                                            0,                              // [in] UINT flags
                                            D3D10_SDK_VERSION,              // [in] SDK Version
                                            &swapChainDesc,                 // [in] Swap Chain descriptor
                                            &swapChain,                   // [out] IDXGISwapChain** SwapChain ptr
                                            &device);                     // [out] ID3D10Device** Device ptr
    if(FAILED(result))
    {

        return false;
    }
    
    bOk = true;

    m_renderer->setDevice(device);
    m_renderer->setSwapChain(swapChain);
    m_renderer->initializeRenderer();
    return bOk;
}

//=============================================================================
/**
**  <summary>Finds optimal display mode</summary>
**
*/
bool DirectX_10_GraphicsDriver::findDisplayMode( IDXGIOutput* pOutput  )
{
    bool bOk = true;
    HRESULT result = S_OK;

    DXGI_MODE_DESC* displayModeList;

    if( !pOutput )
    {
        pOutput = m_pCurrentOutput;
    }

    if( pOutput )
    {
        unsigned int numModes = 0;

        // Get the number of modes that fit the DXGI_FORMAT_R8G8B8A8_UNORM display format for the adapter output (monitor).
        result = pOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &numModes, NULL);
        if(FAILED(result))
        {
            return false;
        }

        // Create a list to hold all the possible display modes for this monitor/video card combination.
        displayModeList = new DXGI_MODE_DESC[numModes];
        if(!displayModeList)
        {
            return false;
        }

        // Now fill the display mode list structures.
        result = pOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &numModes, displayModeList);
        if(FAILED(result))
        {
            return false;
        }

        // Now go through all the display modes and find the one that matches the screen width and height.
        // When a match is found store the numerator and denominator of the refresh rate for that monitor.
        for( unsigned int i=0; i<static_cast<int>(numModes); i++)
        {
            if(displayModeList[i].Width == (unsigned int)m_renderer->m_window.width())
            {
                if(displayModeList[i].Height == (unsigned int)m_renderer->m_window.height())
                {
                    m_screenRefreshRate.Numerator = displayModeList[i].RefreshRate.Numerator;
                    m_screenRefreshRate.Denominator = displayModeList[i].RefreshRate.Denominator;
                }
            }
        }
    }

    
     // Release the display mode list.
    delete [] displayModeList;
    displayModeList = 0;

    return true;
}

//=============================================================================
/**
**  <summary>Gets information about currently selected adapter</summary>
**
*/
bool DirectX_10_GraphicsDriver::getCurrentAdapterInfo()
{
    if( m_pCurrentAdapter )
    {
        HRESULT result;
        unsigned int stringLength;
        int error;
        DXGI_ADAPTER_DESC adapterDesc;

        // Get the adapter (video card) description.
        result = m_pCurrentAdapter->GetDesc(&adapterDesc);
        if(FAILED(result))
        {
            return false;
        }

        // Store the dedicated video card memory in megabytes.
        m_videoCardMemory = ( unsigned int)(adapterDesc.DedicatedVideoMemory / 1024 / 1024);

        // Convert the name of the video card to a character array and store it.
        error = wcstombs_s(&stringLength, m_videoCardDescription, 128, adapterDesc.Description, 128);
        if(error != 0)
        {
            return false;
        }
    }
    return true;
}

//=============================================================================
/**
**  <summary> This will initialize the DirectX_10_ Graphics driver class</summary>
**
*/
bool DirectX_10_GraphicsDriver::initialize()
{
    HRESULT result;

    D3D10_TEXTURE2D_DESC            depthBufferDesc;
    D3D10_DEPTH_STENCIL_DESC        depthStencilDesc;
    D3D10_DEPTH_STENCIL_VIEW_DESC   depthStencilViewDesc;
    D3D10_VIEWPORT viewport;
    D3D10_RASTERIZER_DESC rasterDesc;

    // Initialize the render
    m_renderer = new DirectX_10_Renderer;

    // Get proper window size from the kernel

    unsigned int window_width = 960;
    unsigned int window_height = 600;
    bool         bFullscreen = false;

    // Now as we've got the factory, get the kernel pointer
    Omen::IKernel& pKernel = GetOmenFactory().getKernel();
    
    Omen::PropertyObject windowParams;
    if( pKernel.getPropertyObject( L"window", windowParams ) )
    {
        if( windowParams.hasProperty( L"width" ) )
            window_width = windowParams.property( L"width" );
        if( windowParams.hasProperty( L"height" ) )
            window_height = windowParams.property( L"height" );
        if( windowParams.hasProperty( L"fullscreen" ) )
            bFullscreen = windowParams.property( L"fullscreen" );
    }

    m_renderer->m_window.createWindow(window_width,window_height,bFullscreen);


    // Store the vsync setting.
    m_vsync_enabled = true; // This should be taken from a config file

    // Create the DXGI factory interface
    if( create_DXGI_Factory() )
    {
        enumerate_DXGI_Adapters();
        m_pCurrentAdapter = m_vDXGIAdapters.front();

        // Get information about the adapter
        getCurrentAdapterInfo();
        
        // Enumerate the primary adapter output (monitor).
        if( enumerate_DXGI_AdapterOutputs(m_pCurrentAdapter) )
        {
            // Get the first adapter output found
            m_pCurrentOutput = m_mapDXGIOutputs[m_pCurrentAdapter].front();

            if( findDisplayMode(m_pCurrentOutput) )
            {
                // Create Swap Chain
                if( create_SwapChain() )
                {

                    // Get the pointer to the back buffer.
                    result = m_renderer->swapChain()->GetBuffer(0, __uuidof( ID3D10Texture2D), (LPVOID*)&m_renderer->refBackBuffer());
                    if(FAILED(result))
                    {
                        return false;
                    }

                    // Create the render target view with the back buffer pointer.
                    result = m_renderer->device()->CreateRenderTargetView(m_renderer->backBuffer(), NULL, &m_renderer->refRenderTargetView());
                    if(FAILED(result))
                    {
                        return false;
                    }

                    // Release pointer to the back buffer as we no longer need it.
                    m_renderer->backBuffer()->Release();
                    m_renderer->refBackBuffer() = 0;

                    // Initialize the description of the depth buffer.
                    ZeroMemory(&depthBufferDesc, sizeof(depthBufferDesc));

                    // Set up the description of the depth buffer.
                    depthBufferDesc.Width                = m_renderer->m_window.width();
                    depthBufferDesc.Height               = m_renderer->m_window.height();
                    depthBufferDesc.MipLevels            = 1;
                    depthBufferDesc.ArraySize            = 1;
                    depthBufferDesc.Format               = DXGI_FORMAT_D24_UNORM_S8_UINT;
                    depthBufferDesc.SampleDesc.Count     = 1;
                    depthBufferDesc.SampleDesc.Quality   = 0;
                    depthBufferDesc.Usage                = D3D10_USAGE_DEFAULT;
                    depthBufferDesc.BindFlags            = D3D10_BIND_DEPTH_STENCIL;
                    depthBufferDesc.CPUAccessFlags       = 0;
                    depthBufferDesc.MiscFlags            = 0;

                    // Create the texture for the depth buffer using the filled out description.
                    result = m_renderer->device()->CreateTexture2D(&depthBufferDesc, NULL, &m_renderer->refDepthStencilBuffer());
                    if(FAILED(result))
                    {
                        return false;
                    }

                    // Initialize the description of the stencil state.
                    ZeroMemory(&depthStencilDesc, sizeof(depthStencilDesc));

                    // Set up the description of the stencil state.
                    depthStencilDesc.DepthEnable    = true;
                    depthStencilDesc.DepthWriteMask = D3D10_DEPTH_WRITE_MASK_ALL;
                    depthStencilDesc.DepthFunc      = D3D10_COMPARISON_LESS;

                    depthStencilDesc.StencilEnable      = true;
                    depthStencilDesc.StencilReadMask    = 0xFF;
                    depthStencilDesc.StencilWriteMask   = 0xFF;

                    // Stencil operations if pixel is front-facing.
                    depthStencilDesc.FrontFace.StencilFailOp        = D3D10_STENCIL_OP_KEEP;
                    depthStencilDesc.FrontFace.StencilDepthFailOp   = D3D10_STENCIL_OP_INCR;
                    depthStencilDesc.FrontFace.StencilPassOp        = D3D10_STENCIL_OP_KEEP;
                    depthStencilDesc.FrontFace.StencilFunc          = D3D10_COMPARISON_ALWAYS;

                    // Stencil operations if pixel is back-facing.
                    depthStencilDesc.BackFace.StencilFailOp         = D3D10_STENCIL_OP_KEEP;
                    depthStencilDesc.BackFace.StencilDepthFailOp    = D3D10_STENCIL_OP_DECR;
                    depthStencilDesc.BackFace.StencilPassOp         = D3D10_STENCIL_OP_KEEP;
                    depthStencilDesc.BackFace.StencilFunc           = D3D10_COMPARISON_ALWAYS;

                    // Create the depth stencil state.
                    result = m_renderer->device()->CreateDepthStencilState(&depthStencilDesc, &m_renderer->refDepthStencilState());
                    if(FAILED(result))
                    {
                        return false;
                    }

                    // Set the depth stencil state on the D3D device.
                    m_renderer->device()->OMSetDepthStencilState(m_renderer->depthStencilState(), 1);

                    // Initailze the depth stencil view.
                    ZeroMemory(&depthStencilViewDesc, sizeof(depthStencilViewDesc));

                    // Set up the depth stencil view description.
                    depthStencilViewDesc.Format         = DXGI_FORMAT_D24_UNORM_S8_UINT;
                    depthStencilViewDesc.ViewDimension  = D3D10_DSV_DIMENSION_TEXTURE2D;
                    depthStencilViewDesc.Texture2D.MipSlice = 0;

                    // Create the depth stencil view.
                    result = m_renderer->device()->CreateDepthStencilView(m_renderer->depthStencilBuffer(), &depthStencilViewDesc, &m_renderer->refDepthStencilView());
                    if(FAILED(result))
                    {
                        return false;
                    }

                    // Bind the render target view and depth stencil buffer to the output render pipeline.
                    m_renderer->device()->OMSetRenderTargets(1, &m_renderer->refRenderTargetView(), m_renderer->depthStencilView());

                    // Setup the raster description which will determine how and what polygons will be drawn.
                    rasterDesc.AntialiasedLineEnable = false;
                    rasterDesc.CullMode = D3D10_CULL_BACK;
                    rasterDesc.DepthBias = 0;
                    rasterDesc.DepthBiasClamp = 0.0f;
                    rasterDesc.DepthClipEnable = true;
                    rasterDesc.FillMode = D3D10_FILL_SOLID;
                    rasterDesc.FrontCounterClockwise = false;
                    rasterDesc.MultisampleEnable = false;
                    rasterDesc.ScissorEnable = false;
                    rasterDesc.SlopeScaledDepthBias = 0.0f;

                    // Create the rasterizer state from the description we just filled out.
                    result = m_renderer->device()->CreateRasterizerState(&rasterDesc, &m_renderer->refRasterState());
                    if(FAILED(result))
                    {
                        return false;
                    }

                    // Now set the rasterizer state.
                    m_renderer->device()->RSSetState(m_renderer->rasterState());

                    // Setup the viewport for rendering.
                    viewport.Width = m_renderer->m_window.width();
                    viewport.Height = m_renderer->m_window.height();
                    viewport.MinDepth = 0.0f;
                    viewport.MaxDepth = 1.0f;
                    viewport.TopLeftX = 0;
                    viewport.TopLeftY = 0;

                    // Create the viewport.
                    m_renderer->device()->RSSetViewports(1, &viewport);

                    // Initialize world and ortho matrices
                    m_renderer->initializeMatrices();
                } // ~createSwapChain
            } // ~findDisplayMode
        } // ~DXGI Adapter outputs enumerated
    } // ~DXGI Factory creation succeeded

    return true;
}